#pragma once

#if defined (__unix__) || defined(__MACH__)
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <time.h>
/* 性能测试用(仅用于Unix) */
#define TIME_STAMP(pts)			do{ clock_gettime(CLOCK_MONOTONIC, (pts)); } while(0)
#define TIME_DELTA_MS(pts, pdt)		((unsigned long long)((pdt)->tv_sec  - (pts)->tv_sec) * 1000 + ((pdt)->tv_nsec  - (pts)->tv_nsec) / 1000000)
#define TIME_DELTA_US(pts, pdt)		((unsigned long long)((pdt)->tv_sec  - (pts)->tv_sec) * 1000000 + ((pdt)->tv_nsec  - (pts)->tv_nsec) / 1000 )
#define TIME_DELTA_NS(pts, pdt)		((unsigned long long)((pdt)->tv_sec  - (pts)->tv_sec) * 1000000000 + ((pdt)->tv_nsec  - (pts)->tv_nsec) )



#include <unix/coreaff.h>

#define __PERF_PARALLEL_FUNC_BINDCORE_CUSTOM(func, tlstid, op_nr, max_threads, startcore, perloopinit, thread_nrs, dbg_print)\
static word_t test_##func##_threads_nr;						\
static tid_t test_##func##_threads_tid;						\
static void * test_##func##_warmup(void *arg){					\
	word_t target_thread_nr = (word_t)arg;					\
	while (atm_ld(&test_##func##_threads_tid) <= target_thread_nr){ PAUSE();}\
	return NULL;								\
}\
static void * __test_##func##_wrapper(word_t target_thread_nr){			\
	struct timespec ts, td;							\
	faa(&test_##func##_threads_nr, 1);					\
	while (atm_ld(&test_##func##_threads_nr) < target_thread_nr){};		\
	TIME_STAMP(&ts);							\
	func((void *)(op_nr));							\
	TIME_STAMP(&td);							\
	return (void *)TIME_DELTA_US(&ts, &td);					\
}\
static void * test_##func##_wrapper(void *arg){					\
	word_t target_thread_nr = (word_t)arg;					\
	tid_t tid = faa(&test_##func##_threads_tid, 1);				\
	tlstid = tid;								\
	BIND_CORE((tid + startcore) % HDEF_NATIVE_CPU_NR);			\
	return __test_##func##_wrapper(target_thread_nr);			\
}										\
static float test_##func (){							\
	pthread_t p[max_threads];						\
	void *tret[max_threads];						\
	word_t tidx = 0;							\
	pthread_t warmup[WARMUP_THREAD_NR];				\
	word_t sum;								\
	float ret;								\
	word_t half_threads = 1;						\
	word_t thread_nr;							\
	word_t arr_nr = sizeof(thread_nrs) / sizeof(word_t);			\
	tlstid = 0;								\
	for (tidx = 0; tidx < arr_nr; tidx += 1){				\
	thread_nr = thread_nrs[tidx];						\
	atm_st(&test_##func##_threads_tid, 1);					\
	atm_st(&test_##func##_threads_nr, 0);					\
	perloopinit ;								\
	/* warm up唤醒sleep核心 */						\
	for (word_t idx = 0; idx < WARMUP_THREAD_NR; idx ++){		\
		pthread_create_wrapper(&warmup[idx], NULL, test_##func##_warmup, (void *)thread_nr);\
	}									\
	for (word_t idx = 0; idx < thread_nr - 1; idx++){			\
		pthread_create(&p[idx], NULL, test_##func##_wrapper, (void *)thread_nr);\
	}									\
	BIND_CORE((0 + startcore) % HDEF_NATIVE_CPU_NR);			\
	/* 所有线程绑核心完毕 */						\
	while(atm_ld(&test_##func##_threads_tid) < thread_nr){ if (thread_nr > HDEF_NATIVE_CPU_NR) { sched_yield();} else {PAUSE();} }\
	/* 令warmup线程结束 */							\
	faa(&test_##func##_threads_tid, 1);					\
	for (word_t idx = 0; idx < WARMUP_THREAD_NR; idx++)		\
	{									\
		pthread_join(warmup[idx], NULL);				\
	}									\
	/* 发令枪开始 */							\
	sum = (word_t)__test_##func##_wrapper(thread_nr);			\
	for (word_t idx = 0; idx < thread_nr - 1; idx++)			\
	{									\
		pthread_join(p[idx], &tret[idx]);					\
	}									\
	for (word_t idx = 0; idx < thread_nr - 1; idx++){			\
		sum += (word_t)tret[idx];					\
	}									\
	if (sum == 0) { fatal ("[%lu]%s time == 0, error\n", thread_nr, #func);}\
	dbg_print("%s %lu %f\n", #func, thread_nr, ((float)op_nr)/ (sum / thread_nr) );\
	ret = ((float)op_nr)/ (sum / thread_nr);				\
	CLR_BIND();								\
	}									\
	return ret;								\
}


#define __PERF_PARALLEL_FUNC_BINDCORE(func, op_nr, max_threads, startcore, perloopinit)\
static word_t test_##func##_threads_nr;						\
static tid_t test_##func##_threads_tid;						\
static void * __test_##func##_wrapper(word_t target_thread_nr){			\
	struct timespec ts, td;							\
	faa(&test_##func##_threads_nr, 1);					\
	while (atm_ld(&test_##func##_threads_nr) < target_thread_nr);		\
	TIME_STAMP(&ts);							\
	func(NULL);								\
	TIME_STAMP(&td);							\
	return (void *)TIME_DELTA_US(&ts, &td);					\
}\
static void * test_##func##_wrapper(void *arg){					\
	word_t target_thread_nr = (word_t)arg;					\
	tid_t tid = faa(&test_##func##_threads_tid, 1);				\
	BIND_CORE((tid + startcore) % HDEF_NATIVE_CPU_NR);			\
	return __test_##func##_wrapper(target_thread_nr);			\
}										\
static void test_##func (){							\
	pthread_t p[max_threads];						\
	void *tret[max_threads];						\
	word_t sum;								\
	word_t half_threads = 1;						\
	for (word_t thread_nr = 1; thread_nr <= max_threads; thread_nr += half_threads){\
	atm_st(&test_##func##_threads_tid, 1);					\
	atm_st(&test_##func##_threads_nr, 0);					\
	BIND_CORE((0 + startcore) % HDEF_NATIVE_CPU_NR);			\
	perloopinit ;								\
	for (word_t idx = 0; idx < thread_nr - 1; idx++){			\
		  pthread_create(&p[idx], NULL, test_##func##_wrapper, (void *)thread_nr);\
	}									\
	while(atm_ld(&test_##func##_threads_tid) < thread_nr){ sched_yield(); } \
	sum = (word_t)__test_##func##_wrapper(thread_nr);			\
	for (word_t idx = 0; idx < thread_nr - 1; idx++)			\
	{									\
		  pthread_join(p[idx], &tret[idx]);				\
	}									\
	for (word_t idx = 0; idx < thread_nr - 1; idx++){			\
		  sum += (word_t)tret[idx];					\
	}									\
	if (sum == 0) fatal ("[%lu]%s time == 0, error\n", thread_nr, #func);\
	/* dbg_printf("[%lu]%s(%lu)\t%lu ms\t\t%lu kop/s\n", thread_nr, #func, (word_t)op_nr, sum/thread_nr, (op_nr)  / (sum / thread_nr) ); */\
	dbg_printf("%s %lu %f\n", #func, thread_nr, ((float)op_nr)/ (sum / thread_nr) );\
	if (thread_nr != 1 && is_powerof2(thread_nr)) half_threads = thread_nr >> 1;\
	}\
	/* dbg_printf("\n"); */							\
}


#define __PERF_PARALLEL_FUNC(func, op_nr, opnr_thread_mul, max_threads, perloopinit)			\
static word_t test_##func##_threads_nr;							\
static void * __test_##func##_wrapper(word_t target_thread_nr){				\
	struct timespec ts, td;								\
	faa(&test_##func##_threads_nr, 1);						\
	while (atm_ld(&test_##func##_threads_nr) < target_thread_nr);			\
	TIME_STAMP(&ts);								\
	func((void *)(op_nr));								\
	TIME_STAMP(&td);								\
	return (void *)TIME_DELTA_US(&ts, &td);						\
}\
static void * test_##func##_wrapper(void *arg){						\
	word_t target_thread_nr = (word_t)arg;						\
	return __test_##func##_wrapper(target_thread_nr);				\
}											\
static void test_##func (){								\
	pthread_t p[max_threads];							\
	void *tret[max_threads];							\
	word_t sum;									\
	word_t half_threads = 1;							\
	for (word_t thread_nr = 1; thread_nr <= max_threads; thread_nr += half_threads){\
	atm_st(&test_##func##_threads_nr, 0);						\
	perloopinit ;									\
	for (word_t idx = 0; idx < thread_nr - 1; idx++){				\
		  pthread_create(&p[idx], NULL, test_##func##_wrapper, (void *)thread_nr);\
	}										\
	sum = (word_t)__test_##func##_wrapper(thread_nr);				\
	for (word_t idx = 0; idx < thread_nr - 1; idx++)				\
	{										\
		  pthread_join(p[idx], &tret[idx]);					\
	}										\
	for (word_t idx = 0; idx < thread_nr - 1; idx++){				\
		  sum += (word_t)tret[idx];						\
	}										\
	if (sum == 0) fatal ("[%lu]%s time == 0, error\n", thread_nr, #func);		\
	/* dbg_printf("[%lu]%s(%lu)\t%lu ms\t\t%lu kop/s\n", thread_nr, #func, (word_t)op_nr, sum/thread_nr, (op_nr)  / (sum / thread_nr) ); */\
	dbg_printf("%s %lu %f\n", #func, thread_nr, ((float)op_nr * opnr_thread_mul)/ (sum / thread_nr) );\
	if (thread_nr != 1 && is_powerof2(thread_nr)) half_threads = thread_nr >> 1;\
	}\
	/* dbg_printf("\n"); */							\
}
#define PERF_PARALLEL_FUNC_THREADNR(func, op_nr, max_threads)	__PERF_PARALLEL_FUNC(func, op_nr, thread_nr, max_threads, )

#define PERF_PARALLEL_FUNC(func, op_nr, max_threads)	__PERF_PARALLEL_FUNC(func, op_nr, 1, max_threads, )
#define PERF_PARALLEL_RUN(func)				test_##func()

#endif
